Self-Ask 自问自答多跳推理
Self-Ask 让模型在回答前显式地写出「我还需要先知道什么」,把一个复杂问题拆成一串子问题,逐个回答,最后合成终答。它管的是「问题分解」这一层控制流。
Self-Ask:自问自答的多跳推理
阅读提示 面向了解 Chain-of-Thought(CoT,思维链)和基础 RAG 检索的学习者。 本篇重点解释 Self-Ask 与 CoT 的机制差异,以及它为什么天然适合接搜索引擎。 ⚠️ = 常见陷阱 🆚 = 对比说明 💡 = 选择建议
目录
核心概念 Self-Ask 让模型在回答前显式地写出「我还需要先知道什么」,把一个复杂问题拆成一串子问题,逐个回答,最后合成终答。它管的是「问题分解」这一层控制流。
一、一句话定义
遇到复杂问题时,先不断问自己一些更简单的问题,逐个答完再合成最终答案。
二、最小示例:一次三跳推理
问题:
爱因斯坦出生城市所在国家的首都是哪里?模型不直接作答,而是走这样一段 trace(对应原论文英文模板 Are follow up questions needed here: / Follow up: / Intermediate answer: / So the final answer is:):
需要追问吗:是 ← 只在开头出现这一次
追问:爱因斯坦出生在哪里?
中间答案:Ulm(乌尔姆)
追问:Ulm 在哪个国家?
中间答案:德国
追问:德国的首都是哪里?
中间答案:柏林
所以最终答案是:柏林 ← 靠这一行收尾,不是靠再问一次「需要追问吗」结构:
复杂问题
↓
「需要追问吗:是」 ← 一次性判断,之后不再重复
↓
Follow up → Intermediate answer
↓
Follow up → Intermediate answer (循环若干次)
↓
「所以最终答案是:」 ← 终止信号⚠️ 以为每轮都要问一次「需要追问吗」 错误操作: 把循环写成「生成追问 → 回答 → 再问一次『需要追问吗:是/否』→ 为否则退出」,程序去检测那个「否」。
实际结果: 模型在拆解过程中不会再输出「需要追问吗:否」——它会直接输出
So the final answer is:。于是你的终止检测永远等不到信号,循环靠 max_steps 兜底截断,或者把最终答案当成又一个中间答案继续往下问。(补一句精确的:开头那次判断,模型可以答
No然后直接给答案——机制上是允许的。只是原论文官方 demo 的四个少样本示例全是Yes路径,没给过No的样子,所以模型极少走那条分支;官方代码也只是靠"最后一行还有没有Follow up:"来退出循环,并没有专门处理No。别把「不会出现 No」当成协议保证,但也别把它当终止信号来解析。)原因: 在原论文的模板里,
Are follow up questions needed here:只在问题开头出现一次,用来决定「这题到底要不要拆」。一旦进入拆解,控制流就由Follow up:和So the final answer is:这两个标记接管——前者表示还要再问一跳,后者表示收尾。正确做法: 解析时认两个标记:读到
追问:(Follow up:)就再走一跳,读到所以最终答案是:(So the final answer is:)就停。开头那次「需要追问吗」只用来决定要不要进入 Self-Ask 流程。
三、🆚 和 CoT 的区别:结构化在哪
两者都是「一步步想」,差别在于中间产物的形态。
| 维度 | Chain-of-Thought | Self-Ask |
|---|---|---|
| 中间产物 | 自由散文式推理 | 一问一答的固定结构 |
| 能否被程序解析 | 难,格式不定 | 易,按 追问:/中间答案: 切分 |
| 能否插入外部工具 | 几乎不能 | 能,每个子问题都是天然的检索入口 |
| 事实错误可见性 | 藏在长句里 | 明确落在某个「中间答案」上,好定位 |
关键洞察 Self-Ask 真正的贡献不是「让模型想得更细」,而是把隐式推理变成了有明确边界的槽位。有了槽位,外部系统才有插手的地方——这正是下一节的内容。
四、机制:为什么它天生适合接搜索
因为每个「追问」都是一个独立、自包含、可直接检索的问句。
Self-Ask + Search流程变成:
模型生成到「追问:爱因斯坦出生在哪里?」这一行,就停止生成
↓
程序拦截这个追问,直接丢给搜索引擎(不判断模型确不确定)
↓
search("爱因斯坦出生在哪里?") → "Ulm, Germany"
↓
把检索结果作为「中间答案」写回上下文,让模型接着往下生成
↓
模型生成下一个追问……⚠️ 以为搜索是「模型不确定时才调」 错误操作: 实现时加一层判断——先让模型自己答,答得不确定(低置信度、说「我不清楚」)才去检索。
实际结果: 这层判断本身就不可靠。LLM 对自己编造的事实往往表现得非常确定,恰恰是幻觉最严重的那些答案最不会触发检索。于是该查的没查,不该查的查了一堆。
原因: 原论文的 Self-Ask + Search Engine 实现没有这个分支:每一个生成出来的 follow-up 都交给搜索引擎回答,用检索结果替代模型自己的中间答案。它的价值主张就是「别信模型的记忆,每一跳都去查」。
正确做法: 默认全查。工程上要省钱可以加缓存、或按问题类型(事实型 vs 计算型)分流,但不要用模型的自评置信度当开关。
对比一下直接检索原问题会发生什么:
search("爱因斯坦出生城市所在国家的首都")
→ 命中的多是爱因斯坦传记页面,很难命中"柏林"组合爆炸问题 多跳问题的字面表述在语料里往往根本不存在——没人会写一篇标题叫「爱因斯坦出生城市所在国家的首都」的文档。但拆开后的每一跳(「爱因斯坦出生地」「德国首都」)都是高频、易检索的事实。Self-Ask 把一次检索不到的复合查询,变成了三次都能检索到的简单查询。这就是它在 Multi-hop QA(多跳问答)上有效的根本原因。
五、⚠️ 核心陷阱:子问题跑偏且无人纠正
⚠️ 错误沿着链条放大 错误操作: 信任每个中间答案,直接拿去构造下一个追问,全程不校验。
实际结果:
追问 1:爱因斯坦出生在哪里? 中间答案:Bern(伯尔尼) ← 错了,这是他工作过的城市 追问 2:Bern 在哪个国家? 中间答案:瑞士 ← 这一跳本身完全正确 追问 3:瑞士首都是哪里? 中间答案:伯尔尼 最终答案:伯尔尼 ← 错,且推理过程看起来毫无破绽原因: Self-Ask 是串行依赖结构,第 N 跳的问题由第 N-1 跳的答案构造。第一跳错了之后,后面每一跳都在正确地回答一个错误的问题。而且因为每步局部自洽,最终输出读起来极具说服力——比一段含糊的 CoT 更容易骗过人。
正确做法: 三选一或组合:
- 给事实型子问题接检索,不靠模型记忆(最有效);
- 中间答案带来源,人和程序都能核;
- 关键跳做一致性校验——换个问法再问一次,两次不一致就标记存疑。
⚠️ 简单问题被强行拆解 错误操作: 对所有问题统一套 Self-Ask 模板。
实际结果: 问「Python 的 len() 返回什么」,模型也认真拆出三个追问,token 成本翻三倍,还多了三个出错机会。
原因: 拆解只在「原问题无法被一次检索/一次回忆解决」时才有收益。单跳问题拆解纯属负担。
正确做法: 在少样本示例里补上单跳的例子——
需要追问吗:否后面直接跟所以最终答案是:…。原论文的示例全是多跳,模型看不到「可以不拆」的样板,就会对什么都拆。开头那次「需要追问吗」正是成本闸门,但它只有在示例里出现过「否」的走法时才真正起作用。
六、什么时候用
用:
- Multi-hop QA(多跳问答):答案要串联两个以上事实
- RAG 场景中,用户问题过于复合、单次检索召回差
- 需要让推理链可审计、可溯源的场景
不用:
- 单跳事实问答 → 直接答或直接检索
- 需要探索多种方案(而非串联多个事实)的问题 → 那是 ToT 的领域
💡 Self-Ask 与 ToT 的分工 Self-Ask 是串行的:Q1 → Q2 → Q3,每步只有一条路,答完就往前走。 ToT 是并行的:同一步同时铺开多个候选,评估后剪枝。 判断方法:「我缺的是信息」→ Self-Ask;「我缺的是思路」→ ToT。
七、复习重点
复习重点
- 定义:显式写出追问 → 逐个回答 → 合成终答。
- 控制流的真相:「需要追问吗」只在开头判一次;循环由
追问:推进、由所以最终答案是:终止。别去检测一个不会出现的「需要追问吗:否」。- 与 CoT 的差别:不是想得更细,而是把推理变成可解析的槽位,从而给外部工具留出插入点。
- 为什么配搜索有效:复合问题的字面表述在语料里不存在,拆开后每一跳都是高频可检索事实。原实现是每个 follow-up 都查,不看模型置信度。
- 最危险的坑:串行依赖导致错误放大,且每步局部自洽、外观可信。对策是给事实型子问题接检索、带来源、做一致性校验。
- 第二个坑:别对单跳问题强行拆解——要在少样本示例里补上「不拆」的样板,闸门才生效。
- 它管哪一层:问题分解。缺信息用 Self-Ask,缺思路用 ToT,缺步骤规划用 Plan-and-Execute。
相关笔记:Agent 推理范式总览|Plan-and-Execute 先规划再执行|Reflexion 反思式自我纠错|ToT 思维树